package club.itstudio.IContainer.controller.teacher;

import club.itstudio.IContainer.annotation.LoginRequired;
import club.itstudio.IContainer.annotation.TeacherRequired;
import club.itstudio.IContainer.container.ContainerArgs;
import club.itstudio.IContainer.container.ContainerManager;
import club.itstudio.IContainer.container.ContainerNetwork;
import club.itstudio.IContainer.entity.Container;
import club.itstudio.IContainer.entity.CourseExperiment;
import club.itstudio.IContainer.entity.Image;
import club.itstudio.IContainer.entity.User;
import club.itstudio.IContainer.exception.BaseException;
import club.itstudio.IContainer.exception.ParamInvalidException;
import club.itstudio.IContainer.exception.PermissionException;
import club.itstudio.IContainer.service.*;
import club.itstudio.IContainer.util.Random;
import club.itstudio.IContainer.util.Result;
import club.itstudio.IContainer.util.ResultUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.*;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;

@RequestMapping("/teacher/image")
@RestController
public class TeacherImageController {

    private CourseExperiment experimentForImage;
    private HashMap<Integer, Container> teacherContainerMap = new HashMap<>();

    private ImageService imageService;
    private AuthService authService;
    private JdbcTemplate jdbcTemplate;
    private CourseExperimentService courseExperimentService;
    private ContainerNetwork containerNetwork;
    private ServerPortService serverPortService;
    private ContainerManager containerManager;

    @Autowired
    public void setContainerManager(ContainerManager containerManager) {
        this.containerManager = containerManager;
    }

    @Autowired
    public void setServerPortService(ServerPortService serverPortService) {
        this.serverPortService = serverPortService;
    }

    @Autowired
    public void setContainerNetwork(ContainerNetwork containerNetwork) {
        this.containerNetwork = containerNetwork;
    }

    @Autowired
    public void setCourseExperimentService(CourseExperimentService courseExperimentService) {
        this.courseExperimentService = courseExperimentService;
    }

    @Autowired
    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @Autowired
    public void setImageService(ImageService imageService) {
        this.imageService = imageService;
    }

    @Autowired
    public void setAuthService(AuthService authService) {
        this.authService = authService;
    }

    @LoginRequired
    @TeacherRequired
    @GetMapping
    public Result getImages() {
        User teacher = authService.threadLocalUsers.get();
        String sql = "SELECT\n" +
                "\timage_list.*,\n" +
                "\t`user`.username \n" +
                "FROM\n" +
                "\t( SELECT * FROM image WHERE user_id = ? OR `open` = 1 ) AS image_list\n" +
                "\tJOIN `user` ON `user`.id = image_list.user_id";
        List<HashMap<String, Object>> images = jdbcTemplate.query(sql, (resultSet, i) -> {
            String[] columns = new String[]{"id", "image_name", "created_time", "user_id", "description", "open", "username"};
            HashMap<String, Object> data = new HashMap<>();
            for (String column : columns) {
                data.put(column, resultSet.getObject(column));
            }
            return data;
        }, teacher.id);
        return ResultUtil.success(images);
    }



    @PostMapping
    @LoginRequired
    @TeacherRequired
    public Result startContainerByImage(@RequestParam int imageID) throws IOException {
        // check image exists
        Image image = imageService.getByImageID(imageID);
        if (image == null) throw new ParamInvalidException();

        // image permissions
        User teacher = authService.threadLocalUsers.get();
        if (image.open == 0 && image.userID != teacher.id) throw new PermissionException();

        // teacher created old container
        Container container = teacherContainerMap.get(teacher.id);
        if (container != null) {
            throw new BaseException("容器已存在,请关闭容器后重试");
        }

        // if not exists, create new experiment for network
        if (experimentForImage == null) createNewExperiment(imageID);

        // start container
        ContainerArgs containerArgs = new ContainerArgs();
        containerArgs.setName(String.valueOf(System.currentTimeMillis()));
        containerArgs.setMemoryLimit("1024m");
        containerArgs.setCpuShareLimit("1024");
        containerArgs.setCpuSetLimit("4");
        containerArgs.setNewPassword(Random.getRandomStr(6));
        containerArgs.setNetworkName(experimentForImage.networkName);
        containerArgs.setImageName(image.imageName);
        containerArgs.setSshPort(serverPortService.getRandomServerPort());
        if (containerArgs.getSshPort() == 0) throw new BaseException("当前容器数量过多，请稍后再试");
        containerManager.Create(containerArgs);


        // insert to map
        container = new Container();
        container.containerName = containerArgs.getName();
        container.userID = teacher.id;
        container.status = 1;
        container.sshPort = containerArgs.getSshPort();
        container.sshPassword = containerArgs.getNewPassword();
        teacherContainerMap.put(teacher.id, container);

        return ResultUtil.success();
    }

    @DeleteMapping
    @LoginRequired
    @TeacherRequired
    public Result deleteImage(@RequestParam int imageID) {
        Image image = imageService.getByImageID(imageID);
        if (image == null) throw new ParamInvalidException("镜像不存在");

        User teacher = authService.threadLocalUsers.get();
        if (image.userID != teacher.id) throw new PermissionException("无权限删除此镜像");

        imageService.delete(image.imageName);
        return ResultUtil.success();
    }

    @GetMapping("/container")
    @LoginRequired
    @TeacherRequired
    public Result getTeacherImageContainer() {
        User teacher = authService.threadLocalUsers.get();
        Container container = teacherContainerMap.get(teacher.id);
        if (container == null) throw new BaseException("未创建容器，请先选择镜像创建容器");
        return ResultUtil.success(container);
    }


    @PostMapping("/container")
    @LoginRequired
    @TeacherRequired
    public Result createNewImageByContainer(@RequestParam String imageName, @RequestParam int open,
                                            @RequestParam String description) throws IOException {
        User teacher = authService.threadLocalUsers.get();
        Container container = teacherContainerMap.get(teacher.id);
        if (container == null) throw new BaseException("请先创建容器");

        Image image = imageService.getByImageName(imageName);
        if (image != null) throw new ParamInvalidException("镜像名已存在，请更改");

        containerManager.commit(container.containerName, imageName);

        image = new Image();
        image.userID = teacher.id;
        image.imageName = imageName;
        image.open = open;
        image.createdTime = System.currentTimeMillis();
        image.description = description;

        imageService.insert(image);

        return ResultUtil.success();
    }

    @DeleteMapping("/container")
    @LoginRequired
    @TeacherRequired
    public Result closeContainer() throws IOException {
        User teacher = authService.threadLocalUsers.get();
        Container container = teacherContainerMap.get(teacher.id);
        if (container == null) throw new BaseException("不存在容器");

        containerManager.Stop(container.containerName);
        containerManager.Remove(container.containerName);

        teacherContainerMap.remove(teacher.id);

        return ResultUtil.success();
    }


    private void createNewExperiment(int imageID) {
        // 分配子网
        String subnet = courseExperimentService.subnetAllocate();
        if (subnet.isEmpty()) throw new BaseException("实验数量过多，无法分配子网");

        experimentForImage = new CourseExperiment();
        experimentForImage.experimentName = "Experiment for creating new image";
        experimentForImage.description = "for create new image";
        experimentForImage.courseID = 0;
        experimentForImage.imageID = imageID;
        experimentForImage.containerAliveTime = 100 * 24;
        experimentForImage.memoryLimit = 1024;
        experimentForImage.cpuShareLimit = 1024;
        experimentForImage.cpuSetLimit = 4;
        experimentForImage.endTime = 0;
        experimentForImage.networkName = String.valueOf(System.currentTimeMillis());
        experimentForImage.subnet = subnet;

        //创建子网，保存到数据库
        try {
            containerNetwork.Create("bridge", experimentForImage.subnet, experimentForImage.networkName);
        }catch (IOException e) {
            throw new BaseException("创建子网失败，请联系管理员");
        }
        courseExperimentService.insert(experimentForImage);
    }
}
